SNFAttend = { } -- public interface
SNFAttendData = { } -- saved data

local SNFAttendDataData = { } -- private data -- old set of code
local SNFCurrent = nil

local SNFWaitListExtension = { }  -- note this is not saved!!
local SNFWaitListExtensionExpired = { }  -- note this is not saved!!

local lclRaidID = nil


--[[  SNFAttendData table structure looks like this:

	SNFAttendData - list of raid data tables keyed by a date field, "RaidID".  RaidID looks like '20100303' or CCYYMMDD
	SNFAttendData[RaidID] = {
		"start_in" -- flat list of character names that were in the raid (groups 1-5) when raid start was taken
		"start_wait" -- flat list of character names that got on wait list at raid start
		"end_in" -- flat list of character names that were in the raid (groups 1-5) when raid end was taken
		"end_wait" -- flat list of character names that got on wait list at raid midpoint (for us, 7:00-7:15 server time

		"StartTime" -- hours:minutes of the time when raid start was done
		"EndTime" -- hours:minutes of the time when raid end was done

		"test" -- turned to true when SNFGetTestStatus() is true.  Used to create test raids.  Not fully supported.
		"checksum" -- very primative checksum used by syncing to determine if updates should be done
	}
]]--


local function GetCurrentRaidMembers()
	local lst = { }
	local n = GetNumRaidMembers()

	for i = 1, n do
		local name, rank, subgroup, level, class, fileName,
		  zone, online, isDead, role, isML = GetRaidRosterInfo(i);
		if subgroup < 6 then
			tinsert(lst,SNFPlayer:GetPlayerMain(name))
		else
			SNFDEBUG(3, "Skipping raid member "..name.." because in group "..subgroup)
		end
	end
	return lst
end

local function PopulateStartList(DATE)

	if not SNFAttendData then SNFAttendData = { } end
	if not SNFAttendData[DATE] then SNFAttendData[DATE] = { } end

	if not SNFAttendData[DATE]["start_in"] then
		SNFAttendData[DATE]["start_in"] = GetCurrentRaidMembers()
	end
	SNFPlayer:ClearAllPlayerCounts() -- this clears the counts cache so that attendance factors are recalculated
end

local function PopulateEndList(DATE)

	if not SNFAttendData then SNFAttendData = { } end
	if not SNFAttendData[DATE] then SNFAttendData[DATE] = { } end

	SNFAttendData[DATE]["end_in"] = GetCurrentRaidMembers()
	SNFPlayer:ClearAllPlayerCounts() -- this clears the counts cache so that attendance factors are recalculated
end

local function tcontains(table, name)
	for _, val in ipairs(table) do
		if val == name then return true end
	end
	return false
end

local function tcontainsplayer(table, name)

	-- Make sure name is translated before it's passed into this method.
	for _, val in ipairs(table) do

--		SNFDEBUG(2, "Checking SNFPlayer:GetPlayerMain("..(val or "nil")..") = "..SNFPlayer:GetPlayerMain(val).." to "..name)
		if SNFPlayer:GetPlayerMain(val) == name then return true end
	end
	return false
end

local function ClearRaidChecksum(raidid)
	if SNFAttendData and SNFAttendData[raidid] then SNFAttendData[raidid]["checksum"] = nil end
end


--[[ FOR USE ONLY BY FILTERING LOGIC ]]--
function SNFAttend:GetBaseAttendList()

	return SNFAttendData
end

--[[ PUBLIC METHODS ]]--

function SNFAttend:IsRaid(DATE)

	return (SNFAttendData and SNFAttendData[DATE] ~= nil)
end

function SNFAttend:StartRaid(test, sendMessage)

	local DATE = date("%Y%m%d")

	if not SNFAttendData then SNFAttendData = { } end
	if not SNFAttendData[DATE] then SNFAttendData[DATE] = { } end

	SNFCurrent = SNFAttendData[DATE]

	if SNFAttendData[DATE]["test"] and (not test or test == "") and SNFGetTestStatus() ~= "ON" then
		SNFDEBUG(0, "WARNING! Test status is NOT on, but this raid already is flagged as a test raid!")
		SNFDEBUG(0, "Either turn testing status on, or delete and recreate this raid")
		return false
	end

	hours,minutes = GetGameTime();
	if not SNFCurrent["StartTime"] then -- Don't overwrite if it already exists
		SNFCurrent["StartTime"] = hours..":"..minutes
		SNFAttendData["Source"] = leader  --WHS TODO Where should we be getting this value?  it's not passed in...
	end

	if test and test ~= "" then
		SNFAttendData[DATE]["test"] = test
	elseif SNFGetTestStatus() == "ON" then
		SNFAttendData[DATE]["test"] = true
	end

	PopulateStartList(DATE)
	SNFInitializeDataAuction(DATE)  -- resets LastAuctionNumber

	if (SNFGetTestStatus() == "OFF" and (not test or test == "")) then
		SendChatMessage("Raid Started. Whisper me 'wl add' to get on the wait list.", "GUILD" )
		SendChatMessage("Reminder: you can send me ?loot or ?attend to check the raid data in the mod.", "GUILD" )
	end

	SNFDKP_RaidRegisterEvents()
	ClearRaidChecksum(raidid)
	return DATE

end

function SNFAttend:SNFEndRaid()

	if not SNFCurrent then
		SNFDEBUG(0, "Can't end a raid when one isn't started!")
		return
	end

	hours,minutes = GetGameTime();
	SNFCurrent["EndTime"] = hours..":"..minutes
	SNFCurrent = nil

	PopulateEndList(date("%Y%m%d"))
	if (SNFGetTestStatus() == "OFF") then
		SendChatMessage("Raid end has been taken, you may now leave the raid", "GUILD" )
	end
	SNFDKP_RaidUnregisterEvents()
	ClearRaidChecksum(raidid)
end

function SNFAttend:ListRaids()

	if not SNFAttendData then
		SNFDEBUG(0, "There are no raids recorded.")
		return true
	end
	table.sort(SNFAttendData)
	local srt

	local srt = { }
	for DATE, _ in pairs(SNFAttendData) do
		table.insert(srt, DATE)
	end
	table.sort(srt)

	for _, DATE in ipairs(srt) do
		local data = SNFAttendData[DATE]
		SNFDEBUG(0, "["..DATE.."]")
	end
end

function SNFAttend:GetRaidMembers(DATE, whichlist)

	SNFDEBUG(1, "Dumping raid to UI: "..DATE)

	if SNFRaids and SNFAttendData[DATE] then
		return SNFAttendData[DATE][whichlist]
	end
	return nil
end

function SNFAttend:ListRaidMembers(DATE)

	local which = DATE or date("%Y%m%d")

	SNFDEBUG(1, "Dumping attendance from "..which..".")

	if SNFAttendData and SNFAttendData[which] then
		local start = "[none]"
		if SNFAttendData[which]["start_in"] then
			start = ""
			for _, name in pairs(SNFAttendData[which]["start_in"]) do
				start = start..name..", "
			end
			--start = strjoin(", ", SNFAttendData[which]["start_in"])
		end
		SNFDEBUG(0, "Start raid: "..start);

		local endlist = "[none]"
		if SNFAttendData[which]["end_in"] then
			endlist = ""
			for _, name in pairs(SNFAttendData[which]["end_in"]) do
				endlist = endlist..name..", "
			end
			--endlist = strjoin(", ", SNFAttendData[which]["end_in"])
		end
		SNFDEBUG(0, "End raid: "..endlist);

		start = "[none]"
		if SNFAttendData[which]["start_wait"] then
			start = ""
			for _, name in pairs(SNFAttendData[which]["start_wait"]) do
				start = start..name..", "
			end
			--start = strjoin(", ", SNFAttendData[which]["start_wait"])
		end
		SNFDEBUG(0, "Start Wait: "..start);

		endlist = "[none]"
		if SNFAttendData[which]["end_wait"] then
			endlist = ""
			for _, name in pairs(SNFAttendData[which]["end_wait"]) do
				endlist = endlist..name..", "
			end
			--endlist = strjoin(", ", SNFAttendData[which]["end_wait"])
		end
		SNFDEBUG(0, "End Wait: "..endlist);

		endlist = "[none]"
		if SNFAttendData[which]["wait_list"] then
			endlist = ""
			for _, name in pairs(SNFAttendData[which]["wait_list"]) do
				endlist = endlist..name..", "
			end
			--endlist = strjoin(", ", SNFAttendData[which]["end_wait"])
		end
		SNFDEBUG(0, "Wait List: "..endlist);
	end
end

function SNFAttend:DeleteAttendBefore(DATE)

	for d, _ in pairs(SNFAttendData) do
		if d < DATE then
			SNFAttend:DeleteAttend(d)
		end
	end
end

function SNFAttend:DeleteAttend(DATE)

	SNFDeleteRaidAuctions(DATE)
	if SNFAttendData and DATE and SNFAttendData[DATE] then
		SNFAttendData[DATE] = nil
		SNFDEBUG(0, "Attendance for "..DATE.." deleted!")
	end
	SNFPlayer:ClearAllPlayerCounts() -- this clears the counts cache so that attendance factors are recalculated
end

-- Deletes all raids except the one passed - this is used mostly just in testing for cleaning datasets
function SNFAttend:DeleteAttendBut(DATE)

	if SNFAttendData and DATE then
		for d, _ in pairs(SNFAttendData) do
			if d ~= DATE then
				SNFDeleteRaidAuctions(d)
				SNFAttendData[d] = nil
				SNFDEBUG(0, "Attendance for "..d.." deleted!")
			end
		end
	end
	SNFPlayer:ClearAllPlayerCounts() -- this clears the counts cache so that attendance factors are recalculated
end

function SNFAttend:WaitListAdd(sender)

	local who = SNFPlayer:GetPlayerMain(sender)

	local timeExtended = false

	local hours,minutes = GetGameTime();
	local comparetime = tostring(hours)..tostring(minutes)

	if SNFWaitListExtension[who] and comparetime < SNFWaitListExtension[who] then
		SNFDEBUG(2, who.." is allowed to get by on wait list due to specific exception")
		timeExtended = true
	end

	if not SNFAttendData or not SNFCurrent then -- no raid started
		SendChatMessage("Raid is not started yet - wait for someone to announce in guild chat", "WHISPER", nil, sender)
		return false
	end

	if not SNFCurrent["wait_list"] then
		SNFCurrent["wait_list"] = { }
	end

	local thelist = SNFCurrent["wait_list"]

	local do_add = true
	for i, name in ipairs(thelist) do
		if thelist[i] == who then
			SendChatMessage((who or "<unknown>").." is already on the wait list!", "WHISPER", nil, sender)
			do_add = false -- skip if already in the list
		end
	end
	if do_add then
		tinsert(thelist, who)
		SendChatMessage((who or "<unknown>").." has been added to the wait list!", "WHISPER", nil, sender)
		SNFPlayer:ClearPlayerCounts(who) -- this clears the counts cache so that attendance factors are recalculated

	end

	local hour,minute = GetGameTime();

	if hour < 19 or timeExtended then
		do_add = true
		if not SNFCurrent["start_wait"] then SNFCurrent["start_wait"] = { } end
		thelist = SNFCurrent["start_wait"]
		for i, name in ipairs(thelist or { }) do
			if thelist[i] == who then
				SendChatMessage((who or "<unknown>").." is already on the attendance list for starting wait list", "WHISPER", nil, sender)
				SNFDEBUG(2, "Told "..(sender or "nil").." that he was on the wait list based on comparing "..(thelist[i] or "nil").. " with "..(who or "nil")..".")
				do_add = false -- skip if already in the list
			end
		end
		if do_add and (timeExtended or hour < 17 or (hour == 17 and minute < 16)) then
			tinsert(thelist, who)
			SNFPlayer:ClearPlayerCounts(who) -- this clears the counts cache so that attendance factors are recalculated
			SendChatMessage((who or "<unknown>").." is getting attendance credit for starting wait list", "WHISPER", nil, sender)
		end
	end
	if hour >= 19 then

		do_add = true
		if not SNFCurrent["end_wait"] then SNFCurrent["end_wait"] = { } end
		thelist = SNFCurrent["end_wait"]
		for i, name in ipairs(thelist) do
			if thelist[i] == who then
				SendChatMessage((who or "<unknown>").." is already on the attendance list for ending wait list", "WHISPER", nil, sender)
				SNFDEBUG(2, "Told "..(sender or "nil").." that he was on the wait list based on comparing "..(thelist[i] or "nil").. " with "..(who or "nil")..".")
				do_add = false -- skip if already in the list
			end
		end
		if do_add and ((hour == 19 and minute < 16) or timeExtended) then
			tinsert(thelist, who)
			SNFPlayer:ClearPlayerCounts(who) -- this clears the counts cache so that attendance factors are recalculated
			SendChatMessage((who or "<unknown>").." is getting attendance credit for ending wait list", "WHISPER", nil, sender)
		end
	end
	ClearRaidChecksum(raidid)
	return true
end

function SNFAttend:WaitListRemove(sender)

	local who = SNFPlayer:GetPlayerMain(sender)

	if not SNFAttendData or not SNFCurrent then -- no raid started
		SendChatMessage("Raid is not started yet - wait for someone to announce in guild chat", "WHISPER", nil, sender)
		return false
	end

	if SNFCurrent["wait_list"] then
		for i, name in ipairs(SNFCurrent["wait_list"]) do
			if SNFCurrent["wait_list"][i] == who then
				tremove(SNFCurrent["wait_list"], i)
				SendChatMessage((who or "<unknown>").." has been removed from the wait list", "WHISPER", nil, sender)
				SNFPlayer:ClearPlayerCounts(who) -- this clears the counts cache so that attendance factors are recalculated
			end
		end
	end

	local hour,minute = GetGameTime();
	local thelist = SNFCurrent["start_wait"]
	local describe = "start wait list"

	if hour >= 19 then
		thelist = SNFCurrent["end_wait"]
		describe = "end wait list"
	end

	if thelist then
		for i, name in ipairs(thelist) do
			if thelist[i] == who then
				tremove(thelist, i)
				SNFPlayer:ClearPlayerCounts(who) -- this clears the counts cache so that attendance factors are recalculated
				SendChatMessage((who or "<unknown>").."'s attendance credit for "..describe.." has been removed", "WHISPER", nil, sender)
			end
		end
	end
	ClearRaidChecksum(raidid)
end

function SNFAttend:SetRaidSum(raidid, leader, source, auth, starttime, endtime)

	SNFDEBUG(4, "SNFAttend:SetRaidSum()")

	if not SNFAttendData then SNFAttendData = { } end

	local r = SNFAttendData[raidid]

	if not r then
		SNFDEBUG(0, "Raid "..(raidid or "nil").." doesn't exist, adding raid")
		SNFAttendData[raidid] = { }
		r = SNFAttendData[raidid]
	end
	if r["RaidLeader"] and leader ~= r["RaidLeader"] then
		SNFDEBUG(1, "Raid Leader mismatch, aborting sync of raid "..raidid)
		return
	end
--	if r["Authority"] and tonumber(auth) < tonumber(r["Authority"]) then -- WHSTODO string to number error on this line
--		SNFDEBUG(1, "Authority of sync is less than existing, aborting sync")
--		return
--	end
	r["RaidLeader"] = leader
	r["Source"] = source
	r["Authority"] = tonumber(auth)
	r["StartTime"] = starttime
	r["EndTime"] = endtime
end

function SNFAttend:AddRaidList(raidid, whichlist, listdata)
--	SNFDEBUG(4, "SNFAttend:AddRaidList("..(raidid or "nil")..","..(whichlist or "nil")..","..(listdata or "nil")..")")

	local work = listdata

	if not SNFAttendData[raidid] then SNFAttendData[raidid] = { } end

	local r = SNFAttendData[raidid]

	if not r[whichlist] then r[whichlist] = { } end

	local stopme = 25

	while (work and work ~= "" and stopme > 0) do
		local item, rest = strmatch(work, "(.-),(.*)")

--		SNFDEBUG(4, "work: "..(work or "nil"))
--		SNFDEBUG(4, "item: "..(item or "nil").. " rest: "..(rest or "nil"))
		if (item) then
--			SNFDEBUG(2, "Found item: "..(item or "nil"))
			if not tcontains(r[whichlist], item) then
				SNFDEBUG(3, "Inserting item: "..(item or "nil"))
				table.insert(r[whichlist], item)
			end
			work = rest
		elseif work and work ~= "" then
--			SNFDEBUG(4, "SNFAttend:AddRaidList - Tail end check")
			if not tcontains(r[whichlist], work) then
				SNFDEBUG(3, "Inserting item: "..(work or "nil"))
				table.insert(r[whichlist], work)
			end
			work = ""
		end
		stopme = stopme - 1 -- protection for some endless loops I was having during development
	end
	if stopme == 0 then
		SNFDEBUG(1, "STOPME TRIGGERED")
	end
	ClearRaidChecksum(raidid)
end

--[[WHSTODO]]--
function SNFAttend:RemoveRaidList(raidid, whichlist, listdata)
--	SNFDEBUG(4, "SNFAttend:RemoveRaidList("..(raidid or "nil")..","..(whichlist or "nil")..","..(listdata or "nil")..")")

	local work = listdata

	if not SNFAttendData[raidid] then SNFAttendData[raidid] = { } end

	local r = SNFAttendData[raidid]

	if not r[whichlist] then r[whichlist] = { } end

	local stopme = 25

	while (work and work ~= "" and stopme > 0) do
		local item, rest = strmatch(work, "(.-),(.*)")
		if not (item and rest) then
			if #(work) > 0 then
				SNFDEBUG(4, "Taking only work item: "..(work or "nil"))
				item = work
				rest = ""
				work = ""
			end
		end
		if (item) then
			SNFDEBUG(4, "Found item: "..(item or "nil"))
			for n, compare in ipairs(r[whichlist]) do
				if compare == item then
					table.remove(r[whichlist], n)
				end
			end
			work = rest
		end
		stopme = stopme - 1 -- protection for some endless loops I was having during development
	end
	if stopme == 0 then
		SNFDEBUG(1, "STOPME TRIGGERED")
	end
end

local raidsplitcounts = { }

function SNFAttend:ClearRaidSplitCounts()
	raidsplitcounts = { }
end

function SNFAttend:RaidSplitRaidID(raidid,iteminst)

	if not raidsplitcounts[raidid] then raidsplitcounts[raidid] = { } end
	if not raidsplitcounts[raidid][iteminst] then raidsplitcounts[raidid][iteminst] = 0 end
	raidsplitcounts[raidid][iteminst] = raidsplitcounts[raidid][iteminst] + 1

--	if (iteminst ~= "ULDUAR") then
--		SNFDEBUG(3, "Found "..(date or "nil")..", "..(iteminst or "nil").." from raidid: '"..(raidid or "nil").."'")
--	end
	return date, iteminst
end

function SNFAttend:RaidSplitCountsPrintQuestionable()

	local init = false
	for raidid, data in pairs(raidsplitcounts) do
		local num_inst = 0
		local display = { }
		for inst, count in pairs(data) do
			if inst ~= "PROFESSION" then
				num_inst = num_inst + 1
				display[num_inst] = "RaidID: "..(raidid or "nil").." Instance "..(inst or "nil")..": "..(count or "nil")
			end
		end
		if num_inst > 1 then
			if not init then
				init = true
				SNFDEBUG(1, "Displaying multiple instance raids:")
			end
			for _, out in ipairs(display) do
				SNFDEBUG(1, out)
			end
		end
	end
	if not init then
		SNFDEBUG(1, "No multiple instance raids to display.")
	end
end

function SNFAttend:MigrateOldData(oldraids)

	table.sort(oldraids)
	local srt

	local srt = { }
	for raidid, _ in pairs(oldraids) do
		table.insert(srt, raidid)
	end
	table.sort(srt)

	SNFAttendData = nil -- start clean

	local nRaids = 0
	for _, raidid in ipairs(srt) do

		SNFDEBUG(2, "Migrating raid "..(raidid or "nil"))

		local data = SNFRaids[raidid]

		local _, DATE = strmatch(raidid, "(.*)_(.*)")

		if DATE then
			if not SNFAttendData then SNFAttendData = { } end
			if not SNFAttendData[DATE] then SNFAttendData[DATE] = { } end

			SNFAttendData[DATE]["StartTime"] = data["StartTime"]
			SNFAttendData[DATE]["EndTime"] = data["EndTime"]
			SNFAttendData[DATE]["Source"] = data["Source"]

			if data["StartList"] then
				SNFAttendData[DATE]["start_in"] = { }
				SNFtcopy(SNFAttendData[DATE]["start_in"], data["StartList"])
			end

			if data["EndList"] then
				SNFAttendData[DATE]["end_in"] = { }
				SNFtcopy(SNFAttendData[DATE]["end_in"], data["EndList"])
			end

			if data["WaitList"] then
				SNFAttendData[DATE]["start_wait"] = { }
				SNFtcopy(SNFAttendData[DATE]["start_wait"], data["WaitList"])
				SNFAttendData[DATE]["end_wait"] = { }
				SNFtcopy(SNFAttendData[DATE]["end_wait"], data["WaitList"])
			end

			nRaids = nRaids + 1
		else
			SNFDEBUG(2, "DATE not found from raidid '"..(raidid or "nil").."'")
		end
	end

	SNFDEBUG(0, nRaids.." raids migrated.")
end

function SNFAttend:GetRaidsAttended(name)

	if not name then
		SNFDEBUG(0, "Can't search for empty names in raid lists")
		return false
	end

	local realname = SNFPlayer:GetPlayerMain(name)

	local nAttend, nRaids, nDKP = 0,0,0

	if (SNFPlayer:ArePlayerCountsCached(name)) then
		SNFDEBUG(2, "Getting player counts from Cache")
		nAttend, nRaids, nDKP = SNFPlayer:GetPlayerCounts(name)
	else
		local srt = SNFFilter:GetBaseListFromType("attend")

		for _, DATE in ipairs(srt) do
			local data = SNFAttendData[DATE]
			local count_wait, count_in = 0, 0

			if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
				count_in = count_in + 1
			elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
				count_wait = count_wait + 1
			end
			nRaids = nRaids + 1

			-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
			-- Also, if end_in isn't recorded yet, the current raid will only count start
			if not data["attendcredit"] and data["end_in"] then
				if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
					count_in = count_in + 1
				elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
					count_wait = count_wait + 1
				end
				nRaids = nRaids + 1
			end
			nAttend = nAttend + count_in + count_wait
			if count_wait == 2 then
				nDKP = nDKP + 1 -- if you're only on wait list for a night, then you only get 1 DKP
			else
				nDKP = nDKP + count_in + count_wait -- otherwise you get up to 2 DKP.
			end
		end

		-- Now pay attendtion to the filter and get the attendance per the filter.
		nAttend, nRaids = 0,0
		srt = SNFFilter:GetFilteredList("attend", true)

		for _, DATE in ipairs(srt) do
			local data = SNFAttendData[DATE]

			if data then
				local count_wait, count_in = 0, 0

				if data["start_in"] and tcontainsplayer(data["start_in"], realname) then
					count_in = count_in + 1
				elseif data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
					count_wait = count_wait + 1
				end
				nRaids = nRaids + 1

				-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
				-- Also, if end_in isn't recorded yet, the current raid will only count start
				if not data["attendcredit"] and data["end_in"] then
					if data["end_in"] and tcontainsplayer(data["end_in"], realname) then
						count_in = count_in + 1
					elseif data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
						count_wait = count_wait + 1
					end
					nRaids = nRaids + 1
				end
				nAttend = nAttend + count_in + count_wait
			-- DON"T RECALCULATE nDKP - keep the DKP number calculated from all past raids.
			end
		end

		SNFPlayer:SetPlayerCounts(realname, nAttend, nRaids, nDKP)
	end
	--SNFDEBUG(0, "Raids = "..(nRaids / 2).." of "..(nAttend / 2).." which is "..(nRaids / nAttend * 100).."%.")
	return nAttend, nRaids, nDKP
end

function SNFAttend:ListRaidsAttended(name)

	local msg

	if not name then
		SNFDEBUG(0, "Can't search for empty names in raid lists")
		return false
	end

	local realname = SNFPlayer:GetPlayerMain(name)

	if realname ~= name then
		SNFDEBUG(0, name.." ("..realname..") shows up in these raids:")
	else
		SNFDEBUG(0, name.." shows up in these raids:")
	end
	local nRaids, nAttend, nDKP = 0, 0, 0

	local srt = SNFFilter:GetBaseListFromType("attend")

	for _, DATE in ipairs(srt) do
		local data = SNFAttendData[DATE]
		local count_wait, count_in = 0, 0

		if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
			count_in = count_in + 1
		elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
			count_wait = count_wait + 1
		end
		nRaids = nRaids + 1

		-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
		-- Also, if end_in isn't recorded yet, the current raid will only count start
		if not data["attendcredit"] and data["end_in"] then
			if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
				count_in = count_in + 1
			elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
				count_wait = count_wait + 1
			end
			nRaids = nRaids + 1
		end
		nAttend = nAttend + count_in + count_wait
		if count_wait == 2 then
			nDKP = nDKP + 1 -- if you're only on wait list for a night, then you only get 1 DKP
		else
			nDKP = nDKP + count_in + count_wait -- otherwise you get up to 2 DKP.
		end
	end

	nRaids, nAttend = 0, 0
	srt = SNFFilter:GetFilteredList("attend", true)

	for _, DATE in ipairs(srt) do

		local data = SNFAttendData[DATE]
		local count_wait, count_in = 0, 0
		msg = ""
		if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
			msg = msg.."|cFFFFFF00S|r"
			count_in = count_in + 1
		elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
			msg = msg.."|c0000FF00S|r"
			count_wait = count_wait + 1
		else
			msg = msg.."|cFFFF0000S|r"
		end
		nRaids = nRaids + 1

		-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
		-- Also, if end_in isn't recorded yet, the current raid will only count start
		if not data["attendcredit"] and data["end_in"] then
			if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
				msg = msg.."|cFFFFFF00E|r"
				count_in = count_in + 1
			elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
				msg = msg.."|c0000FF00E|r"
				count_wait = count_wait + 1
			else
				msg = msg.."|cFFFF0000E|r"
			end
			nRaids = nRaids + 1
		end
		msg = "["..DATE.."]: "..msg
		SNFDEBUG(0, msg)
		nAttend = nAttend + count_in + count_wait
	end
	SNFDEBUG(0, "Raids = "..(nAttend / 2).." of "..(nRaids / 2).." which is "..(nAttend / nRaids * 100).."%.")
	SNFDEBUG(0, "DKP Total = "..(nDKP)) -- WHSTODO (add current DKP count here as well)
end

function SNFAttend:ListDKP()

	local dkplist = { }
	local name
	for name, _ in pairs(SNFDKPItems) do
		if dkplist[name] then
			SNFDEBUG(0, (name or "nil").." is being overwritten! Check that out!")
		end

		local spent = (SNFLoot:GetLootCount2(name, "DKP") or 0) * 32
		local _, _, earned = SNFAttend:GetRaidsAttended(name)

		dkplist[name] = { }
		dkplist[name]["total"] = earned
		dkplist[name]["spent"] = spent

		local correction = 0
		if (SNFDKPItems[name] and SNFDKPItems[name]["DKP"] and SNFDKPItems[name]["DKP"]["correction"]) then
			correction = SNFDKPItems[name]["DKP"]["correction"]
		end
		dkplist[name]["corr"] = correction

		dkplist[name]["final"] = earned - spent + correction
	end

	table.sort(dkplist)

	SNFDEBUG(0, "DKP (earned/spent) for individuals sorted by name:")
	for name, data in pairs(dkplist) do
		SNFDEBUG(0, (name or "nil")..": "..(dkplist[name]["final"]).." ("..dkplist[name]["total"].."/"..dkplist[name]["spent"]..")" )
	end
	SNFDEBUG(0, "==================================================")
end

function SNFAttend:SetCorrection()

	local dkplist = { }
	local name
	for name, _ in pairs(SNFDKPItems) do
		if dkplist[name] then
			SNFDEBUG(0, (name or "nil").." is being overwritten! Check that out!")
		end

		local spent = (SNFLoot:GetLootCount2(name, "DKP") or 0) * 32
		local _, _, earned = SNFAttend:GetRaidsAttended(name)

		dkplist[name] = { }
		dkplist[name]["total"] = earned
		dkplist[name]["spent"] = spent

		local correction = 0
		if (SNFDKPItems[name] and SNFDKPItems[name]["DKP"] and SNFDKPItems[name]["DKP"]["correction"]) then
			correction = SNFDKPItems[name]["DKP"]["correction"]
		end
		dkplist[name]["corr"] = correction

		dkplist[name]["final"] = earned - spent + correction

		if not SNFDKPItems[name] then SNFDKPItems[name] = { } end
		if not SNFDKPItems[name]["DKP"] then SNFDKPItems[name]["DKP"] = { } end

		SNFDKPItems[name]["DKP"]["correction"] = (SNFDKPItems[name]["DKP"]["correction"] or 0) - dkplist[name]["final"]
	end
end

function SNFAttend:ReplyAttendance(name)

	local msg

	if not name then
		SNFDEBUG(0, "Can't search for empty names in raid lists")
		return false
	end

	local criteria, compareval = SNFFilter:GetFilterSettings("attend")

	if criteria and compareval then
		SNFMessage:QueueTell("Attendance filter is set as "..criteria.." "..compareval, name)
	else
		SNFMessage:QueueTell("Attendance filter is not set.", name)
		SNFMessage:QueueTell("Because the entire raid list is quite long (over 100 entries), I will not send it", name)
		SNFMessage:QueueTell("Please ask me to set a filter for the list so that you only see the last few raids.", name)
		return
	end

	local realname = SNFPlayer:GetPlayerMain(name)

	if realname ~= name then
		SNFMessage:QueueTell(name.." ("..realname..") shows up in these raids.  Send me ?help attend for key", name)
	else
		SNFMessage:QueueTell(name.." shows up in these raids.  Send me ?help attend for key", name)
	end
	local nRaids, nAttend, nDKP = 0, 0, 0

	local srt = SNFFilter:GetBaseListFromType("attend")

	for _, DATE in ipairs(srt) do
		local data = SNFAttendData[DATE]
		local count_wait, count_in = 0, 0

		if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
			count_in = count_in + 1
		elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
			count_wait = count_wait + 1
		end
		nRaids = nRaids + 1

		-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
		-- Also, if end_in isn't recorded yet, the current raid will only count start
		if not data["attendcredit"] and data["end_in"] then
			if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
				count_in = count_in + 1
			elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
				count_wait = count_wait + 1
			end
			nRaids = nRaids + 1
		end
		nAttend = nAttend + count_in + count_wait
		if count_wait == 2 then
			nDKP = nDKP + 1 -- if you're only on wait list for a night, then you only get 1 DKP
		else
			nDKP = nDKP + count_in + count_wait -- otherwise you get up to 2 DKP.
		end
	end

	nRaids, nAttend = 0, 0
	srt = SNFFilter:GetFilteredList("attend", true)

	for _, DATE in ipairs(srt) do

		local data = SNFAttendData[DATE]
		local count_wait, count_in = 0, 0
		msg = ""
		if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
			msg = msg.."S"
			count_in = count_in + 1
		elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
			msg = msg.."W"
			count_wait = count_wait + 1
		else
			msg = msg.."X"
		end
		nRaids = nRaids + 1

		-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
		-- Also, if end_in isn't recorded yet, the current raid will only count start
		if not data["attendcredit"] and data["end_in"] then
			if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
				msg = msg.."E"
				count_in = count_in + 1
			elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
				msg = msg.."W"
				count_wait = count_wait + 1
			else
				msg = msg.."X"
			end
			nRaids = nRaids + 1
		elseif data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
			msg = msg.."W"  -- just do this to display for those checking their end raid wait list
		end
		msg = "["..DATE.."]: "..msg
		SNFMessage:QueueTell(msg, name)
		nAttend = nAttend + count_in + count_wait
	end
	SNFMessage:QueueTell("Raids = "..(nAttend / 2).." of "..(nRaids / 2).." which is "..(nAttend / nRaids * 100).."%.", name)
	local spent = (SNFLoot:GetLootCount2(realname, "DKP") or 0) * 32
	SNFMessage:QueueTell("DKP Total (ever earned) = "..(nDKP).." current: "..(nDKP - spent), name)
end

function SNFAttend:ScanRaidForAttendChanges()


	local EXTEND_TIME = 5
	local DATE = date("%Y%m%d")

--	if not SNFAttendData then SNFAttendData = { } end
--	if not SNFAttendData[DATE] then SNFAttendData[DATE] = { } end

	if not SNFCurrent then
--		SNFDEBUG(4, "No current raid started! Aborting ScanRaidForAttendChanges")
		return false
	end

	local now = GetCurrentRaidMembers()
	local before = SNFAttendData[DATE]["start_in"]
	local wait = SNFAttendData[DATE]["start_wait"]


	-- Only do this logic if we're in a raid.  This prevents it from spamming people if this player leaves the raid,
	-- or if this player is just in another 5-man.
	local IsInRaid = false
	if GetNumPartyMembers() > 4 then IsInRaid = true end

	if before and IsInRaid then
		for _, name in ipairs(before) do
			SNFDEBUG(4, "Checking "..name.." for inclusion in the main raid");
			if (now and not tcontains(now, name)) and not (wait and tcontains(wait, name))
			   and not (SNFWaitListExtension and SNFWaitListExtension[name])
			   and not SNFWaitListExtensionExpired[name] then
				SNFDEBUG(1, "Found that "..name.." is no longer in the main raid");
				SNFMessage:QueueTell("It appears you have left the main raid group and are not yet on the wait list.", name)
				SNFMessage:QueueTell("Please send me a wl add if you wish to be on the wait list.", name)
				SNFMessage:QueueTell("The wait list time has been extended specifically for you, you have "..EXTEND_TIME.." minutes to respond.", name)
				local hours,minutes = GetGameTime();
				minutes = minutes + EXTEND_TIME
				if minutes > 59 then
					minutes = minutes - 60
					hours = hours + 1
				end
				if hours > 23 then
					hours = 23
					minutes = 59
					SNFDEBUG(0, name.." may have trouble getting on wait list because time wraps around at midnight.")
				end
				SNFWaitListExtension[name] = tostring(hours)..tostring(minutes)
			end
		end
	end

	local hours, minutes = GetGameTime()
	local comparetime = tostring(hours)..tostring(minutes)
	for name, extime in pairs(SNFWaitListExtension) do
		if (SNFWaitListExtension[name] < comparetime) then
			SNFDEBUG(0, name.."'s extended timer has expired")
			SNFWaitListExtension[name] = nil
			SNFWaitListExtensionExpired[name] = "X"
		end
	end

end

--[[ Supports Sync ]]--

function SNFAttend:GetData(DATE)

--	SNFDEBUG(4, "SNFAttend:GetData("..(DATE or "nil")..")")
	if DATE and SNFAttendData[DATE] then
		return SNFAttendData[DATE]
	end
--	SNFDEBUG(4, "SNFAttend:GetData returning nil data set")
	return { }
end

local function round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end

function SNFAttend:ReplyRoleAttendanceReport(name)

	local msg

	if not name then
		SNFDEBUG(0, "Can't run report, don't know sender")
		return false
	end

	local criteria, compareval = SNFFilter:GetFilterSettings("attend")

	if criteria and compareval then
		SNFMessage:QueueTell("Attendance filter is set as "..criteria.." "..compareval, name)
	else
		SNFMessage:QueueTell("Please ask me to set the filter before I run this report", name)
		return
	end

	local leadsrole = SNFPlayer:GetRoleofLeader(name)

	if (not leadsrole or leadsrole == "") then
		SNFDEBUG(0, (name or "nil").." has no lead responsibilities. Not running requested report")
		return
	end

	local playerlist = SNFPlayer:GetPlayersForRole(leadsrole)

	if not playerlist then
		SNFDEBUG(0, "Player list for role '"..(leadsrole or "nil").."' is empty!")
		return
	end

	local srt = { }
	for ii, player in ipairs(playerlist) do
		SNFDEBUG(2,"Building attend data for "..(player or "nil"))
		local nRaids, nAttend, nDKP = SNFAttend:BuildAttendData(player, nil, caller)
		local pctAttend = round((nAttend / nRaids * 100), 2)
		srt[player] = { }
		srt[player]["DKP"] = nDKP
		srt[player]["ATT"] = pctAttend
		srt[player]["SORT"] = nAttend / nRaids
		SNFDEBUG(3, string.format("DKP: %03d Attend: %0.2f%% - %s", nDKP, pctAttend, (player or "nil")))
	end -- player loop

	--sort the table so that they are shown in order of decreasing attendance %
	table.sort(srt, function(a,b) return a["SORT"]<b["SORT"] end)

	for player, data in pairs(srt) do
--		local msg = (player or "nil").." - DKP: "..(nDKP or 0).." Attend: "..(nAttend / nRaids * 100).."%"
		local msg = string.format("DKP: % 3d Attend: % 3.2f%% - %s", data["DKP"], data["ATT"], (player or "nil"))
		SNFMessage:QueueTell(msg, name)
	end
end



-- returns raids, attend count, nDKP, message list
function SNFAttend:BuildAttendData(name, bBuildMsg, caller)

	local msgtbl = { }-- build this list if asked for and return it.  Otherwise return an empty list.

	if not name then
		SNFDEBUG(0, "BuildAttendData called from "..(caller or "nil").." with no name")
		return false
	end

	local criteria, compareval = SNFFilter:GetFilterSettings("attend")

	if criteria and compareval then
		tinsert(msgtbl, "Attendance filter is set as "..criteria.." "..compareval)
	else
		tinsert(msgtbl, "Attendance filter is not set.")
		return 0, 0, msg
	end

	local realname = SNFPlayer:GetPlayerMain(name)

	if realname ~= name and bBuildMsg and bBuildMsg == true then
		tinsert(msgtbl, name.." ("..realname..") shows up in these raids.  Send me ?help attend for key")
	else
		tinsert(msgtbl, name.." shows up in these raids.  Send me ?help attend for key")
	end
	local nRaids, nAttend, nDKP = 0, 0, 0

	local srt;

	nDKP = SNFPlayer:GetPlayerDKP(name)

	srt = SNFFilter:GetFilteredList("attend", true)

	for _, DATE in ipairs(srt) do

		local data = SNFAttendData[DATE]
		local count_wait, count_in = 0, 0
		msg = ""
		if data and data["start_in"] and tcontainsplayer(data["start_in"], realname) then
			msg = msg.."S"
			count_in = count_in + 1
		elseif data and data["start_wait"] and tcontainsplayer(data["start_wait"], realname) then
			msg = msg.."W"
			count_wait = count_wait + 1
		else
			msg = msg.."X"
		end
		nRaids = nRaids + 1

		-- Notice that an "attendcredit" modifier will cause end not to be listed or counted.
		-- Also, if end_in isn't recorded yet, the current raid will only count start
		if not data["attendcredit"] and data["end_in"] then
			if data and data["end_in"] and tcontainsplayer(data["end_in"], realname) then
				msg = msg.."E"
				count_in = count_in + 1
			elseif data and data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
				msg = msg.."W"
				count_wait = count_wait + 1
			else
				msg = msg.."X"
			end
			nRaids = nRaids + 1
		elseif data["end_wait"] and tcontainsplayer(data["end_wait"], realname) then
			msg = msg.."W"  -- just do this to display for those checking their end raid wait list
		end
		msg = "["..DATE.."]: "..msg
		if bBuildMsg and bBuildMsg == true then
			tinsert(msgtbl, msg)
		end
--		SNFDEBUG(3, "DD: "..(msg or "nil"))
		nAttend = nAttend + count_in + count_wait
	end
	if bBuildMsg and bBuildMsg == true then
		tinsert(msgtbl, "Raids = "..(nAttend / 2).." of "..(nRaids / 2).." which is "..(nAttend / nRaids * 100).."%.")
		local spent = (SNFLoot:GetLootCount2(realname, "DKP") or 0) * 32
		tinsert(msgtbl, "DKP Total (ever earned) = "..(nDKP).." current: "..(nDKP - spent))
	end

	if bBuildMsg and bBuildMsg == true then
		return nRaids, nAttend, nDKP, msgtbl
	else
		return nRaids, nAttend, nDKP, { }
	end
end

local function CheckForRaidMatching(composite, lst)

	SNFDEBUG(3, "CheckForRaidMatching("..(composite or "nil")..", { })")
	if not composite then
		return false;
	end
	local raidid, attendchk, auctionchk = strmatch(composite, "(.-):(.-):(.*)")

	SNFDEBUG(3, "Checking for matching raid based on: "..(composite or "nil"))
	if raidid and attendchk and auctionchk then
		-- advanced check will decide if the list is up to date based on a checksum or date
		if not SNFAttendData[raidid] then
			SNFDEBUG(3, "Advanced check found a raidid that was not populated: "..(raidid or "nil"))
			table.insert(lst, raidid)
		else
			if not SNFAttendData[DATE]["checksum"] then
				GenerateRaidChecksum(DATE)
			end
			if SNFAttendData[DATE]["checksum"] ~= tonumber(attendchk) then
				SNFDEBUG(3, "Adv raid chk: ["..(raidid or "nil").."] "..SNFAttendData[DATE]["checksum"].." vs "..(attendchk or "nil"))
				table.insert(lst, raidid)
			else
				if not SNF_AuctionChecksumMatch(raidid, auctionchk) then
					table.insert(lst, raidid)
				else
					SNFDEBUG(3, "Everything matched for raid "..(raidid or "nil"))
				end
			end
		end
	else
		-- backup check just looks to see if we have the raidid
		if not SNFAttendData[composite] then
			table.insert(lst, composite)
			SNFDEBUG(3, "Default check found a raidid that was not populated: "..(raidid or "nil"))
		end
	end
end

function SNFAttend:FindMissingRaids(listdata)

	SNFDEBUG(3, "FindMissingRaids("..(listdata or "nil")..")")
	local work = listdata
	local retval = { }

	local stopme = 25

	while (work and work ~= "" and stopme > 0) do
		local item, rest = strmatch(work, "(.-),(.*)|*")

--		SNFDEBUG(4, "work: "..(work or "nil"))
--		SNFDEBUG(4, "item: "..(item or "nil").. " rest: "..(rest or "nil"))
		if (item) then
			CheckForRaidMatching(item, retval)
--			SNFDEBUG(2, "Found item: "..(item or "nil"))
			work = rest
		elseif work and strtrim(work) ~= "" then
--			SNFDEBUG(4, "SNFAttend:AddRaidList - Tail end check")
			CheckForRaidMatching(work, retval)
			work = ""
		end
		stopme = stopme - 1 -- protection for some endless loops I was having during development
	end
	if stopme == 0 then
		SNFDEBUG(1, "STOPME TRIGGERED")
	end
	return retval
end

local function GenerateRaidChecksum(raidid)
	if not SNFAttendData[raidid] then return 0 end

--		"start_in" -- flat list of character names that were in the raid (groups 1-5) when raid start was taken
--		"start_wait" -- flat list of character names that got on wait list at raid start
--		"end_in" -- flat list of character names that were in the raid (groups 1-5) when raid end was taken
--		"end_wait" -- flat list of character names that got on wait list at raid midpoint (for us, 7:00-7:15 server time
	SNFDEBUG(4, "RaidID: "..(raidid or "nil"))

	local start_in = SNFGenerateFlatListChecksum(SNFAttendData[raidid]["start_in"])
	SNFDEBUG(4, "  start_in is "..start_in)
	local start_wait = SNFGenerateFlatListChecksum(SNFAttendData[raidid]["start_wait"])
	SNFDEBUG(4, "  start_wait is "..start_wait)
	local end_in = SNFGenerateFlatListChecksum(SNFAttendData[raidid]["end_in"])
	SNFDEBUG(4, "  end_in is "..end_in)
	local end_wait = SNFGenerateFlatListChecksum(SNFAttendData[raidid]["end_wait"])
	SNFDEBUG(4, "  end_wait is "..end_wait)

	local retval = (start_in + start_wait + end_in + end_wait) % 100000

	SNFDEBUG(3, "Raid checksum ["..(raidid or "nil").."]: "..retval)
	SNFAttendData[raidid]["checksum"] = retval
	return retval
end

function SNFAttend:GenerateRaidChecksum1(raidid)

	return GenerateRaidChecksum(raidid)
end

-- Used by smartsync to generate a list of all raids.
function SNFAttend:BuildListofRaids()

	if not SNFAttendData then
		SNFDEBUG(2, "There are no raids recorded. BuildListofRaids returned nothing")
		return { }
	end

	local lst = { }
	for DATE, _ in pairs(SNFAttendData) do

		if (SNFAttendData[DATE]) then
			local attendchk = 0
			local auctionchk = 0
			if not SNFAttendData[DATE]["checksum"] then
				GenerateRaidChecksum(DATE)
			end
			attendchk = SNFAttendData[DATE]["checksum"] or 0
			auctionchk = SNF_GenerateAuctionChecksum(DATE)
			table.insert(lst, DATE..":"..attendchk..":"..auctionchk)
		end
	end


	return lst
end

--Version used in testing small sets.
function SNFAttend:BuildListofRaids2()

	if not SNFAttendData then
		SNFDEBUG(2, "There are no raids recorded. BuildListofRaids returned nothing")
		return { }
	end

	local lst = { }
	do
		DATE = '20100401'

		if (SNFAttendData[DATE]) then
			local attendchk = 0
			local auctionchk = 0
			if not SNFAttendData[DATE]["checksum"] then
				GenerateRaidChecksum(DATE)
			end
			attendchk = SNFAttendData[DATE]["checksum"] or 0
			auctionchk = SNF_GenerateAuctionChecksum(DATE)
			table.insert(lst, DATE..":"..attendchk..":"..auctionchk)
		end
	end


	return lst
end

